#include "Platoon.h"

Platoon::Platoon(BWAPI::Player* player)
	: Context(), mUnits(UnitGroup()), mFormation(Formation()), mPlayer(player)
{
	initialize();
}

Platoon::Platoon(UnitGroup units, BWAPI::Player* player) 
	: mUnits(units), mFormation(Formation()), mPlayer(player)
{
	initialize();
}

Platoon::Platoon(const std::set<BWAPI::Unit*> units, BWAPI::Player* player)
	: mUnits(UnitGroup::getUnitGroup(units)), mFormation(Formation()), mPlayer(player)
{
	initialize();
}

void Platoon::initialize()
{
	mFormation.units() = &mUnits;
	mState = new PlatoonIdleState(this);
}

// -------------------------------------------------------------------------
// Context|FSM methods

void Platoon::request()
{
	mState->handle();
}

// -------------------------------------------------------------------------
// Unit management methods

void Platoon::unitShowed(BWAPI::Unit* unit)
{
	//mVisibleUnits.insert(unit);
}

void Platoon::unitDiscovered(BWAPI::Unit *unit)
{
	//mUnits.insert(unit);
}

void Platoon::unitEvaded(BWAPI::Unit *unit)
{
	//mVisibleUnits.erase(std::remove(mVisibleUnits.begin(), mVisibleUnits.end(), unit), mVisibleUnits.end());
}

void Platoon::unitDestroyed(BWAPI::Unit *unit)
{
	mUnits.erase(std::remove(mUnits.begin(), mUnits.end(), unit), mUnits.end());
}

void Platoon::unitCreated(BWAPI::Unit *unit)
{
	mUnits.insert(unit);
}

// -------------------------------------------------------------------------
// Movement methods

void Platoon::move(BWAPI::Position targetPosition, bool relativePosition)
{
	BWAPI::Position oldCenter = mUnits.getCenter();
	BWAPI::Position relativeToCenter;

	std::set<BWAPI::Unit*>::iterator it;
	for(it = mUnits.begin(); it != mUnits.end(); ++it)
	{
		BWAPI::Unit* unit = *it;
		relativeToCenter = unit->getPosition() - oldCenter;

		if (!relativePosition)
			unit->move(targetPosition + relativeToCenter);
		else
			unit->move(oldCenter + targetPosition + relativeToCenter);
	}
}

void Platoon::attack(BWAPI::Unit* target)
{
	mUnits.attackUnit(target);
}

void Platoon::attack(BWAPI::Position target, bool relativePosition)
{
	mUnits.attackMove((relativePosition) ? (target + mUnits.getCenter()) : target);
}

void Platoon::stop()
{
	mUnits.stop();
}

bool Platoon::isUnitMoving()
{
	std::set<BWAPI::Unit*>::iterator it;
	for(it = mUnits.begin(); it != mUnits.end(); ++it)
	{
		BWAPI::Unit* unit = *it;
		if(unit->isMoving())
			return true;
	}

	return false;
}


// -------------------------------------------------------------------------
// Formation methods

void Platoon::goLineFormation(double facingAngle, int innerDistance)
{
	mFormation.goLineFormation(facingAngle, innerDistance);
}

void Platoon::goBoxFormation(double facingAngle, int width, int length, int innerDistance)
{
	mFormation.goBoxFormation(facingAngle, width, length, innerDistance);
}

void Platoon::goCircleFormation(double facingAngle, int radius, int innerDistance)
{
	mFormation.goCircleFormation(facingAngle, radius, innerDistance);
}

void Platoon::rotateFormation(double facingAngle, BWAPI::Position anchor)
{
	mFormation.rotateFormation(facingAngle, anchor);
}

// -------------------------------------------------------------------------
// Battle methods

int Platoon::calculatePossibleAttackers(BWAPI::Unit* target)
{
	if(target->getPlayer() == BWAPI::Broodwar->self())
		return 0;

	int result = 0;
	std::set<BWAPI::Unit*>::iterator it;
	for(it = mUnits.begin(); it != mUnits.end(); ++it)
	{
		BWAPI::Unit* unit = *it;
		
		if(unit->isInWeaponRange(target))
			result++;
	}

	return result;
}

// pair.first = numAlliesAttackers
// pair.second = route
std::pair<int,int> Platoon::calculateAlliesAttackers(BWAPI::Unit* target, EnemyManager* enemy)
{
	int route = 0;
	int attackers = 0;

	//UnitGroup& enemies = enemy->getVisibleUnits();
	
	std::set<BWAPI::Unit*>::iterator it;
	for(it = mUnits.begin(); it != mUnits.end(); ++it)
	{
		BWAPI::Unit* unit = *it;
		UnitGroup unitAttackers;

		// Calculates the distance required to reach the target when it is out of scope
		if(!unit->isInWeaponRange(target))
		{
			int unitRoute = (int)unit->getDistance(target->getPosition()) - DRAGOON_RANGE;
			route += unitRoute;
		}

		//attackers += unitAttackers.size();
	}

	return std::pair<int,int>(attackers, route);
}

BWAPI::Unit* Platoon::getBestTarget(EnemyManager* enemy)
{
	std::set<BWAPI::Unit*>& enemies = enemy->getVisibleUnits();
	std::set<BWAPI::Unit*>::iterator it = enemies.begin();

	BWAPI::Unit* result = *(it++);
	double factor = calculateAttackFactor(result, enemy);

	for( ; it != enemies.end(); ++it)
	{
		BWAPI::Unit* tmp = *it;
		double tmpFactor = calculateAttackFactor(tmp, enemy);

		if(tmpFactor > factor)
		{
			result = tmp;
			factor = tmpFactor;
		}
	}

	return result;
}

double Platoon::calculateAttackFactor(BWAPI::Unit* target, EnemyManager* enemy)
{
	double numAttackersFactor		= 1;
	double numAlliesAttackersFactor	= -1;
	double routeFactor				= -1.3;
	double hpFactor					= -1;

	int targetAttackers = calculatePossibleAttackers(target);
	
	std::pair<int, int> tmp = calculateAlliesAttackers(target, enemy);
	double alliesAttackers = tmp.first;
	double route = tmp.second;

	double result = numAttackersFactor * targetAttackers 
						+ numAlliesAttackersFactor * alliesAttackers 
						+ routeFactor * route 
						+ hpFactor * target->getHitPoints();

	return result;
}


// -------------------------------------------------------------------------
// Debug methods

void Platoon::drawDebug()
{
	std::set<BWAPI::Unit*>::iterator it;
	for(it = mUnits.begin(); it != mUnits.end(); ++it)
	{
		BWAPI::Unit* unit = *it;
		
		drawUnderAttackLine(unit);
		drawTargetLine(unit);
		drawWeaponCooldown(unit);
	}
}


void Platoon::drawTargetLine(BWAPI::Unit* unit)
{
	BWAPI::Position position = unit->getPosition();
	BWAPI::Position target;

	if(unit->isAttacking() && (unit->getTarget() || unit->getOrderTarget()))
	{
		if (unit->getTarget())
			target = unit->getTarget()->getPosition();
		else
			target = unit->getOrderTarget()->getPosition();

		BWAPI::Broodwar->drawLine(BWAPI::CoordinateType::Map, position.x(), position.y(), 
			target.x(), target.y(), BWAPI::Colors::Yellow);
	}

	else if(unit->isMoving())
	{
		target = unit->getTargetPosition();

		BWAPI::Broodwar->drawLineMap(position.x(), position.y(), 
			target.x(), target.y(), BWAPI::Colors::Cyan);
	}
}

void Platoon::drawUnderAttackLine(BWAPI::Unit* unit)
{
	BWAPI::Position position = unit->getPosition();
	
	//
}

void Platoon::drawWeaponCooldown(BWAPI::Unit* unit)
{
	static const int RADIUS = 18;
	int radius = RADIUS - unit->getGroundWeaponCooldown() * RADIUS / DRAGOON_COOLDOWN;

	BWAPI::Color color = (radius == RADIUS)? BWAPI::Colors::Green : BWAPI::Colors::Red;

	BWAPI::Broodwar->drawCircleMap(unit->getPosition().x(), unit->getPosition().y(), 
									RADIUS, BWAPI::Colors::White);
	BWAPI::Broodwar->drawCircleMap(unit->getPosition().x(), unit->getPosition().y(), 
									radius, BWAPI::Colors::Teal);
}

